#include <windows.h>
#include <comdef.h>
#include <metahost.h>
#include <mscoree.h>
#pragma comment(lib, "mscoree.lib") 

#include <future>
#include <thread>
#include <string>
using namespace std;

#include "loader.h"

#define REPORT_ERROR(hr) SetEnvironmentVariableW(L"HydraError", _com_error(hr).ErrorMessage());system("start cmd /k set HydraError")

HydraLoader::HydraLoader(void)
    : m_ptrCLR(NULL)
{
    CoInitializeEx(NULL, COINIT_MULTITHREADED);
}


HydraLoader::~HydraLoader(void)
{
    if(m_ptrCLR)
    {
        ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_ptrCLR);
        HRESULT hr = pClrHost->Stop();
        hr = pClrHost->Release();
        m_ptrCLR = NULL;        
    }
    CoUninitialize();
}

// This is a very tricky way, we detect if we are current
//  running in the HydraHost by check if the environment
//  variable 'HydraHost' exists
bool HydraLoader::isHost() const
{
    WCHAR szBuffer[MAX_PATH];
    return GetEnvironmentVariableW(L"HydraHost", szBuffer, MAX_PATH) != 0;    
}

static void loadCLRInternal(ICLRRuntimeHost** ppHost)
{
    ICLRRuntimeHost *pClrHost = NULL;
    ICLRRuntimeInfo *lpRuntimeInfo = NULL;
    ICLRMetaHost *lpMetaHost = NULL;

    HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&lpMetaHost));
    if(FAILED(hr))
    {
        REPORT_ERROR(hr);
        return;
    }

    hr = lpMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&lpRuntimeInfo));
    if(FAILED(hr))
    {
        REPORT_ERROR(hr);
        lpMetaHost->Release();
        return;
    }

    hr = lpRuntimeInfo->GetInterface(
        CLSID_CLRRuntimeHost,
        IID_ICLRRuntimeHost,
        (LPVOID *)&pClrHost);
    if(FAILED(hr))
    {
        REPORT_ERROR(hr);
        lpRuntimeInfo->Release();
        lpMetaHost->Release();
        return;
    }

    hr = pClrHost->Start();
    if(FAILED(hr))
    {
        REPORT_ERROR(hr);
        pClrHost->Release();
        lpRuntimeInfo->Release();
        lpMetaHost->Release();
        return;
    }

    // TODO: we have to use the path that core-native.dll exists instead of current directory
    WCHAR szPath[MAX_PATH];
    GetCurrentDirectoryW(MAX_PATH, szPath);

    DWORD dwRet = 0;
    hr = pClrHost->ExecuteInDefaultAppDomain(
        (wstring(szPath) + L"\\core.dll").c_str(),
        L"Hydra.Core.Hospital", L"CreateParasite", L"", &dwRet);
    if(FAILED(hr))
    {
        REPORT_ERROR(hr);
        pClrHost->Release();
        lpRuntimeInfo->Release();
        lpMetaHost->Release();
        return;
    }

    lpMetaHost->Release();
    lpRuntimeInfo->Release();
    
    // save
    *ppHost = pClrHost;
}

void HydraLoader::loadCLR()
{
    if(!m_ptrCLR)
    {
        // start a thread to load CLR, otherwise it will fail when
        //  calling ICLRRuntimeHost::Start() method
        async(loadCLRInternal, (ICLRRuntimeHost**)&m_ptrCLR);
    }
}